package drds.data_propagate.driver.packets.client;

import drds.data_propagate.driver.packets.AbstractPacket;
import drds.data_propagate.driver.packets.Capability;
import drds.data_propagate.driver.utils.ByteHelper;
import drds.data_propagate.driver.utils.MSC;
import drds.data_propagate.driver.utils.MySQLPasswordEncrypter;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;

public class ClientAuthenticationPacket extends AbstractPacket {

    private int clientCapability = Capability.CLIENT_LONG_PASSWORD | Capability.CLIENT_LONG_FLAG
            | Capability.CLIENT_PROTOCOL_41 | Capability.CLIENT_INTERACTIVE | Capability.CLIENT_TRANSACTIONS
            | Capability.CLIENT_SECURE_CONNECTION | Capability.CLIENT_MULTI_STATEMENTS | Capability.CLIENT_PLUGIN_AUTH;
    @Setter
    @Getter
    private String username;
    @Setter
    @Getter
    private String password;
    @Setter
    @Getter
    private byte charsetNumber;
    @Setter
    @Getter
    private String databaseName;
    @Setter
    @Getter
    private int serverCapabilities;
    @Setter
    @Getter
    private byte[] scrumbleBuff;
    @Setter
    @Getter
    private byte[] authPluginName;

    public void decode(byte[] bytes) {
        // bypass since nowhere to use.
    }

    /**
     * <pre>
     * VERSION 4.1
     *  Bytes                        Name
     *  -----                        ----
     *  4                            client_flags
     *  4                            max_packet_size
     *  1                            charset_number
     *  23                           (filler) always 0x00...
     *  n (Null-Terminated String)   user
     *  n (Length Coded Binary)      scramble_buff (1 + x bytes)
     *  n (Null-Terminated String)   databasename (optional)
     *  n (Null-Terminated String)   auth plugin taskId (optional)
     * </pre>
     *
     * @throws IOException
     */
    public byte[] encode() throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 1. write client_flags
        ByteHelper.writeUnsignedIntLittleEndian(clientCapability, byteArrayOutputStream); // remove
        // client_interactive
        // feature

        // 2. write max_packet_size
        ByteHelper.writeUnsignedIntLittleEndian(MSC.MAX_PACKET_LENGTH, byteArrayOutputStream);
        // 3. write charset_number
        byteArrayOutputStream.write(this.charsetNumber);
        // 4. write (filler) always 0x00...
        byteArrayOutputStream.write(new byte[23]);
        // 5. write (Null-Terminated String) user
        ByteHelper.writeNullTerminatedString(getUsername(), byteArrayOutputStream);
        // 6. write (Length Coded Binary) scramble_buff (1 + x bytes)
        if (StringUtils.isEmpty(getPassword())) {
            byteArrayOutputStream.write(0x00);
        } else {
            try {
                byte[] encryptedPassword = MySQLPasswordEncrypter.scramble411(getPassword().getBytes(), scrumbleBuff);
                ByteHelper.writeBinaryCodedLengthBytes(encryptedPassword, byteArrayOutputStream);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
            }
        }
        // 7 . (Null-Terminated String) databasename (optional)
        if (getDatabaseName() != null) {
            ByteHelper.writeNullTerminatedString(getDatabaseName(), byteArrayOutputStream);
        }
        // 8 . (Null-Terminated String) auth plugin taskId (optional)
        if (getAuthPluginName() != null) {
            ByteHelper.writeNullTerminated(getAuthPluginName(), byteArrayOutputStream);
        }
        // end write
        return byteArrayOutputStream.toByteArray();
    }


    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
        if (databaseName != null) {
            this.clientCapability |= Capability.CLIENT_CONNECT_WITH_DB;
        }
    }


    public void setAuthPluginName(byte[] authPluginName) {
        this.authPluginName = authPluginName;
        if (authPluginName != null) {
            this.clientCapability |= Capability.CLIENT_PLUGIN_AUTH;
        }
    }

}
